/*
 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt;

import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.lang.ref.SoftReference;
import java.util.Arrays;

/**
 * This is the superclass for Paints which use a multiple color
 * gradient to fill in their raster.  It provides storage for variables and
 * enumerated values common to
 * {@code LinearGradientPaint} and {@code RadialGradientPaint}.
 *
 * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
 * @since 1.6
 */
public abstract class MultipleGradientPaint implements Paint {

  /**
   * The method to use when painting outside the gradient bounds.
   *
   * @since 1.6
   */
  public static enum CycleMethod {
    /**
     * Use the terminal colors to fill the remaining area.
     */
    NO_CYCLE,

    /**
     * Cycle the gradient colors start-to-end, end-to-start
     * to fill the remaining area.
     */
    REFLECT,

    /**
     * Cycle the gradient colors start-to-end, start-to-end
     * to fill the remaining area.
     */
    REPEAT
  }

  /**
   * The color space in which to perform the gradient interpolation.
   *
   * @since 1.6
   */
  public static enum ColorSpaceType {
    /**
     * Indicates that the color interpolation should occur in sRGB space.
     */
    SRGB,

    /**
     * Indicates that the color interpolation should occur in linearized
     * RGB space.
     */
    LINEAR_RGB
  }

  /**
   * The transparency of this paint object.
   */
  final int transparency;

  /**
   * Gradient keyframe values in the range 0 to 1.
   */
  final float[] fractions;

  /**
   * Gradient colors.
   */
  final Color[] colors;

  /**
   * Transform to apply to gradient.
   */
  final AffineTransform gradientTransform;

  /**
   * The method to use when painting outside the gradient bounds.
   */
  final CycleMethod cycleMethod;

  /**
   * The color space in which to perform the gradient interpolation.
   */
  final ColorSpaceType colorSpace;

  /**
   * The following fields are used only by MultipleGradientPaintContext
   * to cache certain values that remain constant and do not need to be
   * recalculated for each context created from this paint instance.
   */
  ColorModel model;
  float[] normalizedIntervals;
  boolean isSimpleLookup;
  SoftReference<int[][]> gradients;
  SoftReference<int[]> gradient;
  int fastGradientArraySize;

  /**
   * Package-private constructor.
   *
   * @param fractions numbers ranging from 0.0 to 1.0 specifying the distribution of colors along
   * the gradient
   * @param colors array of colors corresponding to each fractional value
   * @param cycleMethod either {@code NO_CYCLE}, {@code REFLECT}, or {@code REPEAT}
   * @param colorSpace which color space to use for interpolation, either {@code SRGB} or {@code
   * LINEAR_RGB}
   * @param gradientTransform transform to apply to the gradient
   * @throws NullPointerException if {@code fractions} array is null, or {@code colors} array is
   * null, or {@code gradientTransform} is null, or {@code cycleMethod} is null, or {@code
   * colorSpace} is null
   * @throws IllegalArgumentException if {@code fractions.length != colors.length}, or {@code
   * colors} is less than 2 in size, or a {@code fractions} value is less than 0.0 or greater than
   * 1.0, or the {@code fractions} are not provided in strictly increasing order
   */
  MultipleGradientPaint(float[] fractions,
      Color[] colors,
      CycleMethod cycleMethod,
      ColorSpaceType colorSpace,
      AffineTransform gradientTransform) {
    if (fractions == null) {
      throw new NullPointerException("Fractions array cannot be null");
    }

    if (colors == null) {
      throw new NullPointerException("Colors array cannot be null");
    }

    if (cycleMethod == null) {
      throw new NullPointerException("Cycle method cannot be null");
    }

    if (colorSpace == null) {
      throw new NullPointerException("Color space cannot be null");
    }

    if (gradientTransform == null) {
      throw new NullPointerException("Gradient transform cannot be " +
          "null");
    }

    if (fractions.length != colors.length) {
      throw new IllegalArgumentException("Colors and fractions must " +
          "have equal size");
    }

    if (colors.length < 2) {
      throw new IllegalArgumentException("User must specify at least " +
          "2 colors");
    }

    // check that values are in the proper range and progress
    // in increasing order from 0 to 1
    float previousFraction = -1.0f;
    for (float currentFraction : fractions) {
      if (currentFraction < 0f || currentFraction > 1f) {
        throw new IllegalArgumentException("Fraction values must " +
            "be in the range 0 to 1: " +
            currentFraction);
      }

      if (currentFraction <= previousFraction) {
        throw new IllegalArgumentException("Keyframe fractions " +
            "must be increasing: " +
            currentFraction);
      }

      previousFraction = currentFraction;
    }

    // We have to deal with the cases where the first gradient stop is not
    // equal to 0 and/or the last gradient stop is not equal to 1.
    // In both cases, create a new point and replicate the previous
    // extreme point's color.
    boolean fixFirst = false;
    boolean fixLast = false;
    int len = fractions.length;
    int off = 0;

    if (fractions[0] != 0f) {
      // first stop is not equal to zero, fix this condition
      fixFirst = true;
      len++;
      off++;
    }
    if (fractions[fractions.length - 1] != 1f) {
      // last stop is not equal to one, fix this condition
      fixLast = true;
      len++;
    }

    this.fractions = new float[len];
    System.arraycopy(fractions, 0, this.fractions, off, fractions.length);
    this.colors = new Color[len];
    System.arraycopy(colors, 0, this.colors, off, colors.length);

    if (fixFirst) {
      this.fractions[0] = 0f;
      this.colors[0] = colors[0];
    }
    if (fixLast) {
      this.fractions[len - 1] = 1f;
      this.colors[len - 1] = colors[colors.length - 1];
    }

    // copy some flags
    this.colorSpace = colorSpace;
    this.cycleMethod = cycleMethod;

    // copy the gradient transform
    this.gradientTransform = new AffineTransform(gradientTransform);

    // determine transparency
    boolean opaque = true;
    for (int i = 0; i < colors.length; i++) {
      opaque = opaque && (colors[i].getAlpha() == 0xff);
    }
    this.transparency = opaque ? OPAQUE : TRANSLUCENT;
  }

  /**
   * Returns a copy of the array of floats used by this gradient
   * to calculate color distribution.
   * The returned array always has 0 as its first value and 1 as its
   * last value, with increasing values in between.
   *
   * @return a copy of the array of floats used by this gradient to calculate color distribution
   */
  public final float[] getFractions() {
    return Arrays.copyOf(fractions, fractions.length);
  }

  /**
   * Returns a copy of the array of colors used by this gradient.
   * The first color maps to the first value in the fractions array,
   * and the last color maps to the last value in the fractions array.
   *
   * @return a copy of the array of colors used by this gradient
   */
  public final Color[] getColors() {
    return Arrays.copyOf(colors, colors.length);
  }

  /**
   * Returns the enumerated type which specifies cycling behavior.
   *
   * @return the enumerated type which specifies cycling behavior
   */
  public final CycleMethod getCycleMethod() {
    return cycleMethod;
  }

  /**
   * Returns the enumerated type which specifies color space for
   * interpolation.
   *
   * @return the enumerated type which specifies color space for interpolation
   */
  public final ColorSpaceType getColorSpace() {
    return colorSpace;
  }

  /**
   * Returns a copy of the transform applied to the gradient.
   *
   * <p>
   * Note that if no transform is applied to the gradient
   * when it is created, the identity transform is used.
   *
   * @return a copy of the transform applied to the gradient
   */
  public final AffineTransform getTransform() {
    return new AffineTransform(gradientTransform);
  }

  /**
   * Returns the transparency mode for this {@code Paint} object.
   *
   * @return {@code OPAQUE} if all colors used by this {@code Paint} object are opaque, {@code
   * TRANSLUCENT} if at least one of the colors used by this {@code Paint} object is not opaque.
   * @see java.awt.Transparency
   */
  public final int getTransparency() {
    return transparency;
  }
}
